<?php
	/*
	 *	Kris' Lightweight Framework (KLF)
	 *
	 *	@author Kristian Oye <kristianoye@gmail.com> 
	 *	@copyright (C) 2010-2011, Kristian Oye
	 */
	namespace KLF
	{
		use KLF\System\KType;
		use KLF\System\KApplication;
		use KLF\System\Config\KConfiguration;
		use KLF\System\Errors\KArgumentException;
		use KLF\System\Errors\KPropertyException;
		use KLF\System\Config\KDomainConfiguration;
		use KLF\System\Errors\KInvalidOperationException;
		use KLF\System\Config\KWebApplicationConfiguration;
		
		/**
		 * The character used for separating namespaces.
		 */
		defined('KLF_NS_SEPARATOR')
			or define('KLF_NS_SEPARATOR', '\\');
	
		/**
		 * The base of the framework directory.
		 */
		defined('KLF_PATH') 
			or define('KLF_PATH', realpath(dirname(__FILE__)));
			
		/**
		 * The framework version.
		 */
		define('KLF_VERSION', 0.1);
		
		/**
		 *  KCore is the primary helper of the KLF system.
		 *
		 *  @package KLF
		 *  @access access
		 *  @author Kristian Oye <kristianoye@gmail.com>
		 *  @since 0.1
		 */
		final class KCore
		{
			/**
			 * The application that is currently executing.
			 * 
			 * @var KApplication $_Application
			 */
			private static $_Application;
			
			/**
			 * The singleton core.
			 * @var KCore
			 */
			private static $_Instance;
			
			/**
			 * The list of directories searched for autoloads.
			 * @var array $_IncludedDirectories
			 */
			private $_IncludeDirectories;
			
			/**
			 * The collection of loaded classes and their source files.
			 * 
			 * @var array $_LoadedClasses
			 */
			private $_LoadedClasses;
			
			/**
			 * Private constructor for the core
			 *
			 * @author Kristian Oye <kristianoye@gmail.com>
			 * @since 0.1
			*/
			private function __construct()
			{
				spl_autoload_register(array($this, 'Autoload'));
				set_exception_handler(array($this, 'ExceptionHandler'));
				$this->_IncludeDirectories = array();
				$this->_LoadedClasses = array();
			}
			
			/**
			 * Adds additional directories to the include path uses by the autoloader.
			 *
			 * @author Kristian Oye <kristianoye@gmail.com>
			 * @since 0.1
			 * @return bool True if successful, false if not.
			*/
			public static function AddAutoloadDirectories()
			{
				$inst = KCore::Instance();
				foreach(func_get_args() as $dir)
				{
					if(is_array($dir))
					{
						call_user_func_array(array('\KLF\KCore', 'AddAutoloadDirectories'), $dir);
					}
					else if(is_dir($dir))
					{
						array_push($inst->_IncludeDirectories, $dir);
					}
					else
					{
						throw new \KLF\System\Errors\KArgumentException("Unsupported type passed to AddAutoloadDirectories()");
					}
				}
			}
			
			/**
			 * Autoloads a class if it's available.
			 *
			 * @author Kristian Oye <kristianoye@gmail.com>
			 * @since 0.1
			 * @return boolean True if successfully loaded, false if not.
			*/
			private function Autoload($className)
			{
				foreach($this->_IncludeDirectories as $dir)
				{
					$file = realpath($p = $dir 
						. DIRECTORY_SEPARATOR
						. implode(DIRECTORY_SEPARATOR, 
								explode(KLF_NS_SEPARATOR, $className)) 
						. '.php');
					
					if(file_exists($file))
					{
						if (strlen($file) == 0)
							return false;
						
						$classesThen = array_merge(
							get_declared_classes(), 
							get_declared_interfaces());
						
						ob_start();
						include_once($file);
						$foo = ob_get_clean();
						
							$classesNow = array_diff(
								array_merge(get_declared_classes(), 
								get_declared_interfaces()), $classesThen);
						
						foreach($classesNow as $i => $className)
						{
							$match = array();
							$this->_LoadedClasses[$className] = $file;
						}
						
						return (class_exists($className, false) || 
							interface_exists($className, false));
					}
				}
				return false;
			}
			
			/**
			 * Create's an application
			 *
			 * @author Kristian Oye <kristianoye@gmail.com>
			 * @since 0.1
			 * @param mixed $config
			 * @return \KLF\System\KApplication
			*/
			public static function CreateApplication(KConfiguration $config)
			{
				$className = $config->GetApplicationClass();
				$app = new $className($config);
				
				return $app;
			}
			
			/**
			 * Create's a web application
			 *
			 * @author Kristian Oye <kristianoye@gmail.com>
			 * @since 0.1
			 * @param mixed $config
			 * @return \KLF\System\Web\KHttpApplication
			*/
			public static function CreateWebApplication(KConfiguration $config)
			{
				if ($config->GetType() === KDomainConfiguration::GetType())
				{
					$config = $config->GetHostConfigurationFromRequest();
				}
				elseif($config->GetType() !== KWebApplicationConfiguration::GetType())
				{
					throw new KInvalidOperationException(KCore::FS(
						"Argument 1 to CreateWebApplication() must be a valid web configuration type not {type}",
						array('type' => KCore::GetType($config))));
				}
				$className = $config->GetApplicationClass();
				$app = new $className($config);
				
				return $app;
			}
			
			/**
			 * Handles common exceptions.
			 *
			 * @author Kristian Oye <kristianoye@gmail.com>
			 * @since 0.1
			*/
			public function ExceptionHandler($exception)
			{
				print "Encountered an exception: ".$exception->__toString();
			}
			
			/**
			 * Returns a fully-qualified class name using a partial name.
			 * 
			 * @param string $className
			 * @return string
			 */
			public static function FindClassByPartialName($className)
			{
				$matches = array_filter(array_keys(self::$_Instance->_LoadedClasses), function($f) use ($className)
				{
					return (substr($f, strlen($f) - strlen($className)) === $className);
				});
				if (count($matches) === 0)
				{
					throw new KArgumentException(
						KCore::FS("FindClassByPartialName() found 0 matches for {parm}",
							array('parm' => $className)));
				}
				if (count($matches) > 1)
				{
					throw new KArgumentException(
						KCore::FS("FindClassByPartialName() found {count} matches for {parm}",
							array('parm' => $className, 'count' => count($matches))));
				}
				return array_shift($matches);
			}

			/**
			 * Formats a string to the specified locale using the supplied arguments as inputs
			 *
			 * @author Kristian Oye <kristianoye@gmail.com>
			 * @since 0.1
			 * 
			 * @param $strIn The string to be formatted.
			 * @param $arguments The arguments to plug into the formatting string.
			 * @param $locale The locale to translate/format for.
			 * @return string The formatted string
			*/
			public static function FS($strIn, $arguments=array(), $locale='en-US')
			{
				$strOut = $strIn;
				$matches = array();
				
				if(preg_match_all('/\{{1}([^\}]+)\}{1}/', $strIn, $matches) > 0)
				{
					foreach($matches[1] as $match)
					{
						//  straight up replacement
						if(isset($arguments[$match]))
						{
							$strOut = str_replace('{'.$match.'}', $arguments[$match], $strOut);
						}
					}
				}
				return $strOut;
			}
			
			/**
			 * Returns the current application.
			 * 
			 * @return KApplication
			 */
			public static function GetApplication()
			{
				return self::$_Application;
			}
			
			/**
			 * Returns the type of the parameter. 
			 * 
			 * @param mixed $arg
			 * @return string
			 */
			public static function GetType($arg)
			{
				return KType::GetType($arg);
			}
			
			/**
			 * Returns the singleton instance of the KCore class.
			 *
			 * @author Kristian Oye <kristianoye@gmail.com>
			 * @since 0.1
			 * @return KCore
			*/
			public static function Instance()
			{
				return KCore::$_Instance;
			}
			
			/**
			 * Get the framework path
			 *
			 * @author Kristian Oye <kristianoye@gmail.com>
			 * @since 0.1
			 * @return string The path to the core KLF installation.
			*/
			public static function GetFrameworkDirectory()
			{
				return KLF_PATH;
			}
			
			/**
			 * Get the version of the framework.
			 *
			 * @author Kristian Oye <kristianoye@gmail.com>
			 * @since 0.1
			 * @return float The version of the framework.
			*/
			public static function GetVersion()
			{
				return KLF_VERSION;			
			}
			
			/**
			 * Imports a class or namespace.
			 *
			 * @author Kristian Oye <kristianoye@gmail.com>
			 * @since 0.1
			 * @return bool True if successful, false if not
			*/
			public static function Import($namespace='', $inc='')
			{
				$p = str_replace('.', '\\', $inc);
				$imports = 0;
				$inst = KCore::Instance();
				
				foreach($inst->_IncludeDirectories as $dir)
				{
					$path = $dir . DIRECTORY_SEPARATOR . $p;
					if (is_dir($path))
					{
						foreach(glob($path.DIRECTORY_SEPARATOR.'*.php') as $file)
						{
							if(!is_file($file))
								continue;
								
							$match = array();
							if (preg_match('/(?<className>[^\\\]+)\.php$/i', $file, $match) !== false)
							{
								$classesThen = array_merge(get_declared_classes(), get_declared_interfaces());
								include_once($file);
								$classesNow = array_diff(array_merge(get_declared_classes(), get_declared_interfaces()), $classesThen);
								
								foreach($classesNow as $i => $className)
								{
									$match = array();
									self::$_Instance->_LoadedClasses[$className] = $file;
									if (preg_match('/(?<className>[^\\\]+)$/', $className, $match) !== false)
									{
										$classAlias = $namespace . "\\" . $match['className'];
										class_alias($className, $classAlias);
										$imports ++;
									}
								}
							}
						}
						return ($imports > 0);
					}
					else if(file_exists($file = "$path.php"))
					{
						$match = array();
						if (preg_match('/(?<className>[^\\\]+)\.php$/i', $file, $match) !== false)
						{
							$classesThen = array_merge(get_declared_classes(), get_declared_interfaces());
							include_once($file);
							$classesNow = array_diff(array_merge(get_declared_classes(), get_declared_interfaces()), $classesThen);
							
							foreach($classesNow as $i => $className)
							{
								$match = array();
								if (preg_match('/(?<className>[^\\\]+)$/', $className, $match) !== false)
								{
									$classAlias = $namespace . "\\" . $match['className'];
									class_alias($className, $classAlias);
									$imports++;
								}
							}
						}
						return ($imports > 0);
					}
				}
				return false;
			}
			
			/**
			 * Initializes the framework
			 *
			 * @author Kristian Oye <kristianoye@gmail.com>
			 * @since 0.1
			 * @return bool	True if successfully initialized, false if previously initialized.
			*/
			public static function Initialize()
			{
				if (!isset(KCore::$_Instance))
				{
					KCore::$_Instance = new KCore();
					KCore::Instance()->AddAutoloadDirectories(realpath(__DIR__));
					KCore::Instance()->AddAutoloadDirectories(explode(PATH_SEPARATOR, get_include_path()));
					return true;
				}
				return false;
			}
			
			public static function SetApplication(KApplication $app)
			{
				if (!isset(self::$_Application))
				{
					self::$_Application = $app;
				}
				else
				{
					throw new KInvalidOperationException("Illegal attempt to replace running application.");
				}
			}
		}
	
		KCore::Initialize();
	}

	/*
	 *	Kris' Lightweight Framework (KLF)
	 *
	 *	The global namespace helper functions.
	 *
	 *	@author Kristian Oye <kristianoye@gmail.com> 
	 *	@copyright (C) 2010-2011, Kristian Oye
	 */
	namespace
	{
		use \KLF;
		
		/**
		 * Imports a number of namespaces or specific classes into the referenced 
		 * namespace by creating class aliases.
		 * 
		 * @author Kristian Oye <kristianoye@gmail.com>
		 * @param $ns The relative namespace that you are importing into.
		 * @param $list A list of import expressions.
		 */
		function _KImport()
		{
			$args = func_get_args();
			
			for($i=1; $i<count($args); $i++)
			{
				KCore::Import($args[0], $args[$i]);
			}
		}
		
		/**
		 * Format string 
		 * 
		 * @param string $strIn The formatter string
		 * @param array $arguments The arguments to pass into 
		 * @param string $locale The locale of the string.
		 * 
		 * @return string
		 */
		function FS($strIn, $arguments=array(), $locale='en-US')
		{
			$strOut = $strIn;
			$matches = array();
		
			if(preg_match_all('/\{{1}([^\}]+)\}{1}/', $strIn, $matches) > 0)
			{
				foreach($matches[1] as $match)
				{
					//  straight up replacement
					if(isset($arguments[$match]))
					{
						$strOut = str_replace('{'.$match.'}', $arguments[$match], $strOut);
					}
				}
			}
			return $strOut;
		}
		
		/**
		 * Forces all keys in an argument array to be lower case.
		 * 
		 * @param array|object $args
		 * @throws KArgumentException
		 * @return multitype:unknown
		 */
		function func_lower_args($args)
		{
			if (!is_array($args))
			{
				if (is_object($args))
				{
					$o = $args;
					$args = array();
					foreach($o as $key => $value)
					{
						$args[$key] = $value;
					}
				}
				else 
				{
					throw new KArgumentException("Argument 1 to func_lower_args() must be an array");
				}
			}
			
			$result = array();
			foreach($args as $k => $v)
			{
				if (!is_string($k))
				{
					$result[$k] = $v;
				}
				else
				{
					$result[strtolower($k)] = $v;
				}
			}
			return $result;
		}
		
		function get_class_tree($o)
		{
			if (isset($o))
			{
				$result = array();
				$parent = get_parent_class($o);
				
				while($parent)
				{
					array_unshift($result, $parent);
					$parent = get_parent_class($parent);
				}
				array_push($result, get_class($o));
				
				return $result;
			}
		}
		
		/**
		 * Function that returns the namespace of the specified object.
		 * 
		 * @param object $o
		 */
		function get_namespace($o)
		{
			if (is_object($o))
			{
				$namespaces = explode(DIRECTORY_SEPARATOR, get_class($o));
				unset($namespaces[count($namespaces) - 1]);
				
				return join(DIRECTORY_SEPARATOR, $namespaces);
			}
		}
		
		/**
		 * Returns true if the argument passed is a Closure
		 * 
		 * @param mixed $arg
		 * @return boolean
		 */
		function is_closure($arg)
		{
			return (isset($arg) && get_class($arg) === 'Closure');
		}
		
		/**
		 * Returns an array that is made up
		 * Enter description here ...
		 * @param unknown_type $arg
		 */
		function closure_args($arg)
		{
			if (is_closure($arg)) {
				$o = new stdClass();
				$arg($o);
				return (array)$o;
			}
		}
		
		/**
		 * pseudo .NET anonymous object creation.
		 * 
		 * @param Closure $c
		 * @return \KLF\stdClass
		 */
		function _KO($arg1, Closure $arg2 = NULL)
		{
			$c = isset($arg2) ? $arg2 : $arg1;
			if (isset($arg2) && is_string($arg1)) 
			{
				if (!class_exists($arg1))
				{
					throw new KClassNotFoundException($arg1);
				}
				$o = new $arg1();
			}
			else 
			{
				$o = new stdClass();
			}
			$c($o);
			return $o;
		}
	}
	